/*
 * Copyright (C) 2010-2011, Mathieu Labbe and IntRoLab - Universite de Sherbrooke
 *
 * This file is part of SeMoLearning.
 *
 * SeMoLearning is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * SeMoLearning is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with SeMoLearning.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <QtGui/QApplication>
#include <QtGui/QMainWindow>
#include <QtGui/QScrollArea>
#include <QtGui/QGridLayout>
#include <QtGui/QLabel>
#include <QtCore/QBuffer>
#include "utilite/ULogger.h"
#include "utilite/UCv2Qt.h"
#include "utilite/UPlot.h"
#include "utilite/UConversion.h"
#include <semol/core/DBDriver.h>
#include <semol/core/SensoryMemory.h>
#include <semol/gui/PdfPlot.h>
#include <semol/gui/TwistWidget.h>

using namespace semol;

int main(int argc, char * argv[])
{
	if(argc < 4)
	{
		printf("Usage: actionCompare input.db rawInput.db linkIdA linkIdB\n");
		return 1;
	}
	ULogger::setType(ULogger::kTypeConsole);

	std::string database = argv[1];
	std::string databaseRaw = argv[2];
	std::vector<int> actuatorIds;
	for(int i=3; i<argc; ++i)
	{
		actuatorIds.push_back(std::atoi(argv[i]));
	}
	UASSERT(actuatorIds.size() >= 2);

	DBDriver driver;
	DBDriver driverRaw;
	UASSERT(driver.openConnection(database));
	UASSERT(driverRaw.openConnection(databaseRaw));

	std::vector<NeighborLink *> links;
	for(unsigned int i=0; i<actuatorIds.size(); ++i)
	{
		links.push_back(driver.loadLink(actuatorIds[i]));
		UASSERT(links.back() != 0);
	}

	//load sensors
	std::set<int> sensorIds;
	unsigned int maxHistorySize = 0;
	int sensorStep = 1;
	for(unsigned int i=0; i<links.size(); ++i)
	{
		for(std::list<std::vector<int> >::const_iterator iter=links[i]->sensorHistoryIds().begin();
			iter!=links[i]->sensorHistoryIds().end();
			++iter)
		{
			for(unsigned int i=0; i<iter->size(); ++i)
			{
				if(iter->at(i) > 0)
				{
					sensorIds.insert(iter->at(i));
				}
			}
		}
		if(links[i]->sensorHistoryIds().size() > maxHistorySize)
		{
			maxHistorySize = links[i]->sensorHistoryIds().size();
			sensorStep = links[i]->sensorHistoryIds().front().size();
		}
	}

	// load with raw data
	std::map<int, Sensor *> sensors = driver.loadSensors(sensorIds, true);
	SensoryMemory sensoryMem;
	sensoryMem.add(sensors);

	//construct sensory attention from last node
	std::vector<std::vector<float> > sensoryAttention(sensorStep);
	for(unsigned int i=0; i<sensoryAttention.size(); ++i)
	{
		sensoryAttention[i] = std::vector<float>(maxHistorySize, 0.0f);
	}
	int minId = actuatorIds[0]-maxHistorySize;
	if(minId <=0)
	{
		minId = 1;
	}
	UINFO("actuatorIds[0]=%d, minId=%d, maxHistorySize=%d", actuatorIds[0], minId, maxHistorySize);
	std::vector<int> lastSensorIds;
	ParametersMap parameters;
	parameters.insert(ParametersPair(Parameters::kSMAudioDBThreshold(), "-20.0"));
	parameters.insert(ParametersPair(Parameters::kSMAudioIgnoredDCComponents(), "4"));
	parameters.insert(ParametersPair(Parameters::kSMAudioMinMagnitude(), "1"));
	SensoryMemory tmpSensoryMem(parameters);
	int j=maxHistorySize - (actuatorIds[0] - minId);
	QMap<int, QByteArray> imagesMap;
	for(int nodeId=minId; nodeId<actuatorIds[0]; ++nodeId)
	{
		std::list<SensorRaw> data = driverRaw.getRawData(nodeId);
		UASSERT_MSG((int)data.size() >= sensorStep, uFormat("data.size()=%d, sensorStep=%d", (int)data.size(), sensorStep).c_str());
		std::vector<int> sensorIds;
		for(std::list<SensorRaw>::iterator iter=data.begin(); iter!=data.end(); ++iter)
		{
			if(iter->type() == Sensor::kTypeImage ||
				iter->type() == Sensor::kTypeAudioFreqSqrdMagn ||
				iter->type() == Sensor::kTypeTwist)
			{
				sensorIds.push_back(tmpSensoryMem.add(*iter));
				if(iter->type() == Sensor::kTypeImage)
				{
					QImage img = uCvMat2QImage(iter->data());
					QByteArray ba;
					QBuffer buffer(&ba);
					buffer.open(QIODevice::WriteOnly);
					img.save(&buffer, "JPEG"); // writes image into JPEG format
					imagesMap.insert(maxHistorySize-1-j, ba);
				}
			}
		}
		UASSERT((int)sensorIds.size() == sensorStep);

		// compare with last
		if(lastSensorIds.size())
		{
			for(unsigned int i=0; i<sensorIds.size(); ++i)
			{
				float sim = tmpSensoryMem.compare(sensorIds[i], lastSensorIds[i]);
				sensoryAttention[i][j] = 1.0f-sim;
			}
		}
		lastSensorIds = sensorIds;
		++j;
	}
	tmpSensoryMem.clear();

	QApplication app(argc, argv);
	QMainWindow window;

	QScrollArea * scrollArea = new QScrollArea();
	scrollArea->setWidgetResizable(true);
	QWidget * scrollAreaWidgetContent = new QWidget();
	scrollAreaWidgetContent->setObjectName("area");

	UPlot * attentionPlot = new UPlot(scrollAreaWidgetContent);
	attentionPlot->setGraphicsView(true);
	attentionPlot->showLegend(false);
	for(unsigned int i=0; i<sensoryAttention.size(); ++i)
	{
		float max = uMax(sensoryAttention[i]);

		QMap<int, float> data;
		for(unsigned int j=0; j<sensoryAttention[i].size(); ++j)
		{
			sensoryAttention[i][j] /= max;
			data.insert(sensoryAttention[i].size()-1-j, sensoryAttention[i][j]);
		}

		PdfPlotCurve * curve = new PdfPlotCurve(QString("Attention[%1]").arg(i), &imagesMap);
		curve->setPen(attentionPlot->getRandomPenColored());
		curve->setData(data, QMap<int, int>());
		attentionPlot->addCurve(curve, true);
	}

	QVBoxLayout * vLayout = new QVBoxLayout(scrollAreaWidgetContent);

	QGridLayout * gridLayout = new QGridLayout();
	gridLayout->setContentsMargins(1,1,1,1);
	gridLayout->setHorizontalSpacing(1);
	gridLayout->setVerticalSpacing(1);

	vLayout->addWidget(attentionPlot);
	vLayout->addLayout(gridLayout,1);
	vLayout->addStretch(2);

	scrollAreaWidgetContent->setLayout(vLayout);
	scrollArea->setWidget(scrollAreaWidgetContent);

	window.setCentralWidget(scrollArea);

	// fill the header
	for(unsigned int i=0; i<maxHistorySize; ++i)
	{
		gridLayout->addWidget(new QLabel(QString("%1").arg(i), scrollAreaWidgetContent), 0, sensorStep*i+1); // index
	}

	// fill the reference
	std::vector<std::vector<int> > historyRef = uListToVector(links[0]->sensorHistoryIds());
	for(unsigned int i=0; i<historyRef.size(); ++i)
	{
		for(unsigned int j=0; j<historyRef[i].size(); ++j)
		{
			Sensor * s = uValue(sensors, historyRef[i].at(j), (Sensor*)0);
			if(s)
			{
				if(s->type() == Sensor::kTypeImage)
				{
					QLabel * label = new QLabel(scrollAreaWidgetContent);
					label->setPixmap(QPixmap::fromImage(uCvMat2QImage(s->rawData())));
					label->setToolTip(QString("%2").arg(historyRef[i].at(j)));
					gridLayout->addWidget(label, 1, sensorStep*i+j+1);
				}
				else if(s->type() == Sensor::kTypeAudioFreqSqrdMagn)
				{
					UPlot * plot = new UPlot(scrollAreaWidgetContent);
					plot->showLegend(false);
					plot->showAxes(false, false);
					plot->setToolTip(QString("%1").arg(historyRef[i].at(j)));
					plot->addCurve(QString("%1").arg(historyRef[i].at(j)), Qt::red)->setData(s->descriptor());
					gridLayout->addWidget(plot, 1, sensorStep*i+j+1);
				}
				else if(s->type() == Sensor::kTypeTwist)
				{
					TwistWidget * twist = new TwistWidget(s->rawData().at<float>(0), 0, 0, 0, 0, s->rawData().at<float>(5), scrollAreaWidgetContent);
					twist->setToolTip(QString("%1").arg(historyRef[i].at(j)));
					gridLayout->addWidget(twist, 1, sensorStep*i+j+1);
				}
				else
				{
					UFATAL("not handled sensor type");
				}
			}
			else if(historyRef[i].at(j) > 0)
			{
				UFATAL("Not found sensor");
			}
		}
	}
	gridLayout->addWidget(new QLabel(QString("%1").arg(links[0]->actuatorId()), scrollAreaWidgetContent), 1, 0);
	gridLayout->addWidget(new QLabel(QString("%1").arg(links[0]->actuatorId()-historyRef.size()), scrollAreaWidgetContent), 1, sensorStep*historyRef.size()+1);

	for(unsigned int k=1; k<links.size(); ++k)
	{
		printf("Processing %d...\n", links[k]->actuatorId());
		std::vector<std::vector<int> > historyB = uListToVector(links[k]->sensorHistoryIds());
		std::vector<std::set<int> > historySetB(sensorStep);
		for(unsigned int i=0; i<historyB.size(); ++i)
		{
			for(unsigned int j=0; j<historyB[i].size(); ++j)
			{
				historySetB[j].insert(historyB[i][j]);
			}
		}
		std::vector<std::vector<float> > similarities(sensorStep); // for each sensor type -> a vector of similarities
		std::vector<std::vector<float> > similaritiesAppearance(sensorStep); // for each sensor type -> a vector of similarities
		for(unsigned int i=0; i<similarities.size(); ++i)
		{
			similarities[i] = std::vector<float>(historyB.size(), 0.0f);
			similaritiesAppearance[i] = std::vector<float>(historyB.size(), 0.0f);
		}
		for(unsigned int i=0; i<historyB.size(); ++i)
		{
			for(unsigned int j=0; j<historyB[i].size(); ++j)
			{
				Sensor * s = uValue(sensors, historyB[i].at(j), (Sensor*)0);
				float sim = 0.0f;
				if(i<historyRef.size())
				{
					float maxSim = 0.0f;
					for(std::set<int>::iterator iter=historySetB[j].begin(); iter!=historySetB[j].end(); ++iter)
					{
						float tmpSim = sensoryMem.compare(historyRef[i].at(j), *iter);
						if(maxSim < tmpSim)
						{
							maxSim = tmpSim;
						}
					}
					similaritiesAppearance[j][i] = maxSim;
					sim = similarities[j][i] = sensoryMem.compare(historyRef[i].at(j), historyB[i].at(j));
				}
				if(s)
				{
					if(s->type() == Sensor::kTypeImage)
					{
						QLabel * label = new QLabel(scrollAreaWidgetContent);
						label->setPixmap(QPixmap::fromImage(uCvMat2QImage(s->rawData())));
						label->setToolTip(QString("%1(%2)").arg(historyB[i].at(j)).arg(sim));
						gridLayout->addWidget(label, (k-1)*2+2, sensorStep*i+j+1);
					}
					else if(s->type() == Sensor::kTypeAudioFreqSqrdMagn)
					{
						UPlot * plot = new UPlot(scrollAreaWidgetContent);
						plot->showLegend(false);
						plot->showAxes(false, false);
						if(i<historyRef.size())
						{
							Sensor * sRef = uValue(sensors, historyRef[i].at(j), (Sensor*)0);
							if(sRef)
							{
								plot->addCurve(QString("ref%1").arg(historyRef[i].at(j)), Qt::red)->setData(sRef->descriptor());;
							}
						}
						plot->addCurve(QString("%1").arg(historyB[i].at(j)), Qt::blue)->setData(s->descriptor());
						plot->setToolTip(QString("%1(%2)").arg(historyB[i].at(j)).arg(sim));
						gridLayout->addWidget(plot, (k-1)*2+2, sensorStep*i+j+1);
					}
					else if(s->type() == Sensor::kTypeTwist)
					{
						//printf("sim = %f, raw=%f,%f desc=%f,%f vs refDesc=%f,%f\n",
						//		sim, s->rawData().at<float>(0), s->rawData().at<float>(5), s->descriptor()[0], s->descriptor()[1],
						//		sensoryMem.getSensor(historyRef[i].at(j))->descriptor()[0], sensoryMem.getSensor(historyRef[i].at(j))->descriptor()[1]);
						TwistWidget * twist = new TwistWidget(s->rawData().at<float>(0), 0, 0, 0, 0, s->rawData().at<float>(5), scrollAreaWidgetContent);
						twist->setToolTip(QString("%1(%2)").arg(historyB[i].at(j)).arg(sim));
						gridLayout->addWidget(twist, (k-1)*2+2, sensorStep*i+j+1);
					}
					else
					{
						UFATAL("Not handled sensor....");
					}
				}
				else if(historyB[i].at(j) > 0)
				{
					UFATAL("Not found sensor");
				}
				else
				{
					QLabel * label = new QLabel(scrollAreaWidgetContent);
					label->setToolTip(QString("(%2)").arg(sim));
					gridLayout->addWidget(label, (k-1)*2+2, sensorStep*i+j+1);
				}
			}
		}
		int total = historyB.size() > historyRef.size()?historyB.size():historyRef.size();
		if(total)
		{
			std::vector<float> meansAttention(similarities.size(), 0.0f);
			std::vector<float> means(similarities.size(), 0.0f);
			std::vector<float> meansAttentionAppearance(similarities.size(), 0.0f);
			std::vector<float> meansAppearance(similarities.size(), 0.0f);
			QString toolTip;
			for(unsigned int i=0; i<similarities.size(); ++i)
			{
				float sum = 0.0f;
				float sumAttention = 0.0f;
				float sumAppearance = 0.0f;
				float sumAttentionAppearance = 0.0f;
				for(unsigned int j=0; j<similarities[i].size(); ++j)
				{
					sum+=similarities[i][j];
					sumAttention+=similarities[i][j]*sensoryAttention[i][j];
					sumAppearance+=similaritiesAppearance[i][j];
					sumAttentionAppearance+=similaritiesAppearance[i][j]*sensoryAttention[i][j];
				}
				meansAttention[i] = sumAttention/float(total);
				means[i] = sum/float(total);
				meansAttentionAppearance[i] = sumAttentionAppearance/float(total);
				meansAppearance[i] = sumAppearance/float(total);
				toolTip.push_back(QString("%1(%2) | %3(%4)%5").arg(means[i]).arg(meansAttention[i]).arg(meansAppearance[i]).arg(meansAttentionAppearance[i]).arg(i==similarities.size()-1?"":"\n"));
			}
			float mergedSimilarityAttention = uSum(meansAttention)/float(meansAttention.size());
			float mergedSimilarity = uSum(means)/float(means.size());
			float mergedSimilarityAttentionAppearance = uSum(meansAttentionAppearance)/float(meansAttentionAppearance.size());
			float mergedSimilarityAppearance = uSum(meansAppearance)/float(meansAppearance.size());
			toolTip.push_front(QString("%1(%2) | %3(%4)\n\n").arg(mergedSimilarity).arg(mergedSimilarityAttention).arg(mergedSimilarityAppearance).arg(mergedSimilarityAttentionAppearance));
			toolTip.push_front(QString("One to One | Appearance\n"));

			QLabel * labelA = new QLabel(QString("%1").arg(links[k]->actuatorId()), scrollAreaWidgetContent);
			QLabel * labelB = new QLabel(QString("%1").arg(links[k]->actuatorId()-historyB.size()), scrollAreaWidgetContent);
			labelA->setToolTip(toolTip);
			labelB->setToolTip(toolTip);
			gridLayout->addWidget(labelA, (k-1)*2+2, 0);
			gridLayout->addWidget(labelB, (k-1)*2+2, sensorStep*historyB.size()+1);
		}
	}

	gridLayout->setRowStretch(links.size()*2+1,1);

	window.show();
	app.exec();

	for(unsigned int i=0; i<links.size(); ++i)
	{
		delete links[i];
	}
	links.clear();


	return 0;
}
